package models;

import boardGenerator.GameDifficultyLevel;
import boardGenerator.Pair;
import java.util.LinkedList;
import java.util.List;
import java.util.Observable;
import java.util.Random;

/**
 * Clase Partida
 * Implementa una partida Sudoku Observable
 * En esta clase se crea el tablero de la partida actual
 * la solucion, el cronometro, el modelo y otros
 * 
 * Provee las diferentes funcionalidades del juego como
 * Iniciar Nueva Partida, Verificar Jugada, Sugerir Jugada
 * Resolver Tablero, reiniciar Tablero, Insertar Valor
 * Borrar Valor, Calcular Puntuacion y otros
 */
public class Partida extends Observable{    
    private int[][] board;
    private int[][] solution;
    private int numberCount;
    private int corrections;
    private int checks;
    private int suggest;
    private SudokuModelo model;
    private Configuration configuration;
    private int punctuation;    
    private Cronometer cronometro = Cronometer.getInstance();
    int minutes = 0;
    private boolean borro;
    private boolean verifico;
    private boolean pista;    
    
    /**
     * Constructor de la Clase Partida
     * @param nuevaConfig Configuracion de la Partida
     */
    public Partida(Configuration nuevaConfig) {
        configuration = nuevaConfig;
    }
    
    /**
     * Nuva Partida
     * Crea el modelo Sudoku (segun generador) obtiene
     * tableros a resolver y solucion, inicia el tiempo
     * y el contador, calcula la partida inicial e informa
     * al observador los cambios realizados
     */
    public void nuevaPartida(){
        model = new SudokuModelo(configuration.getGenerator(),configuration.getDifficulty());
        board = model.getBoard();
        solution = model.getSolution();
        numberCount = model.getNumberCount();        
        cronometro.iniciarTiempo();
        punctuation = 0;
        corrections = 0;
        checks = 0;
        suggest = 0;
        borro = false; pista = false; verifico = false;
        calcularPuntuacionInicial();
        setChanged();
        notifyObservers("NUMBERCOUNT_CHANGED");
        setChanged();
        notifyObservers("CHANGE_POINTS");
    }

    /**
     * Verifica la Jugada Actual
     * @return Lista de pos x e y donde hay un
     * numero incorrecto
     */
    public List<Pair<Integer,Integer>> verificarJugada() {
        List<Pair<Integer,Integer>> lista = new LinkedList<>();
        for(int i=0;i<9;i++){
            for(int j=0;j<9;j++){
                if( (board[i][j] != solution[i][j]) && (board[i][j] != 0)){
                    Pair<Integer,Integer> casilla = new Pair(i,j);
                    lista.add(casilla);                    
                }
            }
        }
        checks++;
        verifico = true;
        calcularPuntuacion();
        return lista;                    
    }
    
    /**
     * Calcula una posible jugada y notifica al
     * observador con el valor y la posicion en donde
     * puede poner ese numero correctamente
     */
    public void sugerirJugada(){
        if (numberCount == 81) return;
        Random rdmGen = new Random();
        int i;
        int j;
        do {
            i = rdmGen.nextInt(9);
            j = rdmGen.nextInt(9);
        } while (board[i][j]==solution[i][j]);
        suggest++;
        pista = true;
        calcularPuntuacion();
        Pair<Pair<Integer,Integer>,Integer> tmp = new Pair<Pair<Integer,Integer>,Integer>();
        Pair<Integer,Integer> pos = new Pair(i,j);
        tmp.setFirst(pos);tmp.setSecond(solution[i][j]);  
        setChanged();
        notifyObservers(tmp);
    }
    
    /**
     * Resuelve la partida Actual
     */
    public void resolver(){
        board = solution;
        punctuation = 0;
        numberCount = 81;
        setChanged();
        notifyObservers("NUMBERCOUNT_CHANGED");
        setChanged();
        notifyObservers("CHANGE_POINTS");
    }
    
    /**
     * Reinicia el tablero al problema original
     * pero el tiempo y la puntuacion sigen como estaban
     */
    public void resetBoard(){
        this.board = model.getBoard();
        numberCount = model.getNumberCount();
        setChanged();
        notifyObservers("NUMBERCOUNT_CHANGED");
    }

    /**
     * Inserta un valor en la grilla problema
     * @param i filas
     * @param j columnas
     * @param valor a insertar
     */
    public void InsertarValor(int i,int j,int valor){
        if(board[i][j] == 0){            
            numberCount++;            
        } else {
            if(board[i][j] != valor){
                corrections++;
                borro = true;
                calcularPuntuacion();
            }
        }
        board[i][j]= valor;
        setChanged();
        notifyObservers("NUMBERCOUNT_CHANGED");        
    }
    
    /**
     * Borra un valor en la grilla problema
     * pone un 0 en el lugar que habia un numero
     * @param i fila
     * @param j columna
     */
    public void BorrarValor(int i,int j){
        if(board[i][j] == 0) return;
        numberCount--;       
        corrections++;
        board[i][j]= 0;
        borro = true;
        calcularPuntuacion();
        setChanged();
        notifyObservers("NUMBERCOUNT_CHANGED");
    }
    
    /**
     * Obtiene el tiempo de la partida actual
     * @return 
     */
    public String getTiempoPartida() {
        return this.cronometro.obtenerTiempo();
    }

    /**
     * Obtiene la puntuacion de la partida actual
     * @return 
     */
    public int getPuntuacion() {
        return punctuation;
    }

    /**
     * Obtiene el tablero de la partida actual
     * @return 
     */
    public int[][] getTablero() {
        return board;
    }
    
    /**
     * Calcula la puntuacion inicial
     * de la partida segun dificultad
     */
    private void calcularPuntuacionInicial(){
        if (configuration.getDifficulty()== GameDifficultyLevel.EASY)
            punctuation = 6000;
        if (configuration.getDifficulty()== GameDifficultyLevel.MODERATE)
            punctuation = 8000;
        if (configuration.getDifficulty()== GameDifficultyLevel.HARD)
            punctuation = 10000;
    }
    
    /**
     * Calcula la puntuacion ante cambios en el tablero
     * segun la dificultad actual
     */
    public void calcularPuntuacion(){
        minutes = cronometro.obtenerMinutos();
        if (minutes != 0){
            if (configuration.getDifficulty()== GameDifficultyLevel.EASY)
                punctuation += -(minutes*5);
            if (configuration.getDifficulty()== GameDifficultyLevel.MODERATE)
                punctuation += -(minutes*10);
            if (configuration.getDifficulty()== GameDifficultyLevel.HARD)
                punctuation += -(minutes*15);
        }
        if (pista){
            if (configuration.getDifficulty()== GameDifficultyLevel.EASY)
                punctuation += -(suggest*50);
            if (configuration.getDifficulty()== GameDifficultyLevel.MODERATE)
                punctuation += -(suggest*150);
            if (configuration.getDifficulty()== GameDifficultyLevel.HARD)
                punctuation += -(suggest*200);
            pista = false;
        }
        if (borro){
            if (configuration.getDifficulty()== GameDifficultyLevel.EASY)
                punctuation += -(corrections*15);
            if (configuration.getDifficulty()== GameDifficultyLevel.MODERATE)
                punctuation += -(corrections*20);
            if (configuration.getDifficulty()== GameDifficultyLevel.HARD)
                punctuation += -(corrections*25);
            borro = false;
        }
        if (verifico){
            if (configuration.getDifficulty()== GameDifficultyLevel.EASY)
                punctuation += -(checks*20);
            if (configuration.getDifficulty()== GameDifficultyLevel.MODERATE)
                punctuation += -(checks*25);
            if (configuration.getDifficulty()== GameDifficultyLevel.HARD)
                punctuation += -(checks*30);
            verifico = false;
        }     
        setChanged();
        notifyObservers("CHANGE_POINTS");
    }
    
    /**
     * Obtiene la configuracion de la partida actual
     * @return 
     */
    public Configuration getConfig(){
        return configuration;
    }
    
    /**
     * Obtiene la instancia del cronometro de la partida actual
     * @return 
     */
    public Cronometer getCronometro(){
        return this.cronometro;
    }
    
    /**
     * Obtiene la cantidad de numeros que faltan colocar en la partida actual
     * @return 
     */
    public int getCantNumbersToPut(){
        return 81-numberCount;
    }
}